module windump.plugins.ie.ie7;

import std.stdio;

import std.string;
import std.ctype;
import std.utf;

import windump.scanning.detector;

import windump.registry;
import windump.w32api;
import windump.util;
import windump.utf;
import windump.dll;

import std.utf;

import windump.plugins.ie.iecommon;

const string RegistryPath = `Software\Microsoft\Internet Explorer\IntelliForms\Storage2`;

const uint MaxCacheEntrySize = 4096;

debug import std.stdio;

extern(Windows){
// ---------------------- WININET.DLL -----------------------------------
//
enum : DWORD
{
    NORMAL_CACHE_ENTRY =              0x00000001,
    STICKY_CACHE_ENTRY =              0x00000004,
    EDITED_CACHE_ENTRY =              0x00000008,
    TRACK_OFFLINE_CACHE_ENTRY =       0x00000010,
    TRACK_ONLINE_CACHE_ENTRY =        0x00000020,
    SPARSE_CACHE_ENTRY =              0x00010000,
    COOKIE_CACHE_ENTRY =              0x00100000,
    URLHISTORY_CACHE_ENTRY =          0x00200000
}


struct INTERNET_CACHE_ENTRY_INFOW
{  
    DWORD dwStructSize;  
    LPWSTR lpszSourceUrlName;  
    LPWSTR lpszLocalFileName;  
    DWORD CacheEntryType;  
    DWORD dwUseCount;  
    DWORD dwHitRate;  
    DWORD dwSizeLow;  
    DWORD dwSizeHigh;  
    FILETIME LastModifiedTime;  
    FILETIME ExpireTime;  
    FILETIME LastAccessTime;  
    FILETIME LastSyncTime;  
    LPBYTE lpHeaderInfo;  
    DWORD dwHeaderInfoSize;  
    LPWSTR lpszFileExtension;  
    union 
    {    
        DWORD dwReserved;    
        DWORD dwExemptDelta; 
    };
} 
alias INTERNET_CACHE_ENTRY_INFOW* LPINTERNET_CACHE_ENTRY_INFOW;


static DllImport!("wininet.dll", "FindFirstUrlCacheEntryW", 
		HANDLE function(
  			LPCWSTR lpszUrlSearchPattern,
			LPINTERNET_CACHE_ENTRY_INFOW lpFirstCacheEntryInfo,
			LPDWORD lpdwFirstCacheEntryInfoBufferSize)
		) FindFirstUrlCacheEntryW;

static DllImport!("wininet.dll", "FindNextUrlCacheEntryW",
		BOOL function(
			HANDLE hEnumHandle,
			LPINTERNET_CACHE_ENTRY_INFOW lpNextCacheEntryInfo,
			LPDWORD lpdwNextCacheEntryInfoBufferSize)
		) FindNextUrlCacheEntryW;

static DllImport!("wininet.dll", "FindCloseUrlCache", 
		BOOL function(HANDLE hEnumHandle)) FindCloseUrlCache;

} //extern (Windows)



private struct IE7Pair
{
    string username;
    string password;
}

class IE7Detector : Detector!(IE7Detector, "browser/ie7", "table")
{
    public override ResultItem[] update()
    {
        ResultItem[] ret;

        ubyte[][string] regData = getRegData;

        wstring[] wurls = enumCachedUrl();

        foreach(wurl; wurls)
        {
            string url = toUTF8(wurl[0 .. $ - 1]); 

            IE7Pair[] pairs = decrypt(regData, wurl);
            
            if(pairs is null) continue;

            foreach(p; pairs)
            {
                ResultItem item;
                item.provider = name();
                item.host = url;
                item.account = p.username;
                item.password = p.password;
                ret ~= item;
            }
        }

        return ret;
    }

}

// 计算w字符串散列值
string hashString(wstring str)
{
    HCRYPTPROV hProv = null;
    HCRYPTHASH hHash = null;
    CryptAcquireContextW(&hProv, null, null, PROV_RSA_FULL, 0);

    if(CryptCreateHash(hProv, CALG_SHA1, null, 0, &hHash) != TRUE)
        throw new Exception("Failed to create hash");

    if(CryptHashData(hHash, cast(ubyte*)str.ptr, str.length * wchar.sizeof, 0) != TRUE)
        throw new Exception("Failed to HashData");

    // 返回20字节的hash
    DWORD dwHashLen = 20;
    ubyte[20] buffer;

    if(CryptGetHashParam(hHash, HP_HASHVAL, buffer.ptr, &dwHashLen, 0) != TRUE)
        throw new Exception("CryptGetHashParam was failed");

    scope(exit)
    {
        if(hHash !is null)CryptDestroyHash(hHash);
        if(hProv !is null)CryptReleaseContext(hProv, 0);
    }

    string hashed;
    for(uint i = 0; i < dwHashLen; i++)
        hashed ~= format("%2.2X", buffer[i]);

    return hashed;
}

// 枚举所有缓存的 URL
wstring[] enumCachedUrl()
{
    DWORD size = MaxCacheEntrySize;
    ubyte[MaxCacheEntrySize] entryBuffer;

    INTERNET_CACHE_ENTRY_INFOW* ptr = 
        cast(INTERNET_CACHE_ENTRY_INFOW*)(entryBuffer.ptr); 
    //ptr.CacheEntryType = NORMAL_CACHE_ENTRY;// | URLHISTORY_CACHE_ENTRY;
    HANDLE hfind = FindFirstUrlCacheEntryW(null, ptr, &size);
    scope(exit)
        FindCloseUrlCache(hfind);
    ptr.dwStructSize = size;

    wstring[] ret;
    do
    {
        
        if((ptr.CacheEntryType & NORMAL_CACHE_ENTRY) && 
                (ptr.CacheEntryType & URLHISTORY_CACHE_ENTRY))
            //(ptr.CacheEntryType & (NORMAL_CACHE_ENTRY | URLHISTORY_CACHE_ENTRY)) == 
           // (NORMAL_CACHE_ENTRY | URLHISTORY_CACHE_ENTRY)) 
        {
            wstring wurl = 
                ptr.lpszSourceUrlName[0 .. strlenz(ptr.lpszSourceUrlName) + 1].dup;        
            uint begin = 0;
            uint end = 0;
            for(uint i = 0; i < wurl.length; i++)
            {
                if(std.ctype.isalpha(wurl[i])) 
                    wurl[i] = cast(wchar)std.ctype.tolower(wurl[i]);
                if(wurl[i] == '@')begin = i + 1;
                if(wurl[i] == '?' || wurl[i] == '\0'){ //表包含 '?' 后的 url 参数 
                    wurl[i] = '\0';
                    end = i + 1; 
                    break;
                }
            }

            ret ~= wurl[begin .. end];
        }
        size = MaxCacheEntrySize;
    //ptr.CacheEntryType = NORMAL_CACHE_ENTRY;// | URLHISTORY_CACHE_ENTRY;
              
    }while(FindNextUrlCacheEntryW(hfind, ptr, &size) == TRUE);
    return ret;
}

//读取注册表里的数据
ubyte[][string] getRegData()
{
    ubyte[][string] ret;
    scope auto k = Registry.currentUser.openSubkey(RegistryPath);
    uint n = k.valueCount;
    for(uint i = 0; i < n; i++)
    {
        string name = k.getValueName(i);
        auto hash = k.getValue(i).value_BINARY; //the last two bits of value name is not the SHA1 hash for the URL
        ret[name[0 .. $ - 2]] = hash;
    }
    
    return ret;
}

//解密注册表里的数据
IE7Pair[] decrypt(ubyte[][string] regData, wstring url)
in {
    assert(url !is null);
    assert(regData !is null);
}
body {
    string hashed = hashString(url);

    if(!(hashed in regData))
        return null;

    ubyte[] data = regData[hashed];
    if(data is null)
        throw new Exception("Bad Registry Value");

    DATA_BLOB datain, dataout, entropy;
    entropy.cbData = url.length * wchar.sizeof;
    entropy.pbData = cast(ubyte*)url.ptr;
    datain.cbData = data.length;
    datain.pbData = data.ptr;

	if(CryptUnprotectData(
                &datain, null, &entropy, null, null, 0, &dataout) != TRUE)
        throw new Exception("Failed to CryptUnprotectData");

    scope(exit) 
    {
        LocalFree(cast(HANDLE)dataout.pbData);
    }    

    IE7Pair[] ret;
    ubyte* ptr = dataout.pbData + dataout.pbData[0];
    StringIndexHeader* sih = cast(StringIndexHeader*)(ptr);

    if(sih.type != 1)
        throw new Exception("Not supported StringIndexHeader type");
    if(sih.entriesCount != 2)
        throw new Exception("Too many entries");

    IE7Pair pair;
    ubyte* pdata = ptr + sih.structureSize + 
        sih.entriesCount * StringIndexEntry.sizeof;
    auto userEntry = cast(StringIndexEntry*)
        (ptr + sih.structureSize);
    auto passEntry = cast(StringIndexEntry*)
        (ptr + sih.structureSize + StringIndexEntry.sizeof);
    wchar* user = cast(wchar*)(pdata + userEntry.dataOffset);
    wchar* password = cast(wchar*)(pdata + passEntry.dataOffset);
    pair.username = toUTF8(user[0 .. userEntry.dataSize]); //这里的单位是字符，不是字节
    pair.password = toUTF8(password[0 .. passEntry.dataSize]); //ditto

    ret ~= pair;


    return ret;
}

